﻿#if !NET20

using Apewer;
using Apewer.Network;
using System;
using System.Collections.Generic;
using System.Net;

namespace Apewer.WebSocket
{

    /// <summary></summary>
    public sealed class ChatServer
    {

        #region public

        /// <summary></summary>
        public Action<string> ErrorAction { get; set; }

        /// <summary></summary>
        public Action<Exception> ExceptedAction { get; set; }

        /// <summary></summary>
        public Action DisposedAction { get; set; }

        /// <summary></summary>
        public bool WebSocketListening
        {
            get { return (_websocket != null && _websocket.Running); }
        }

        /// <summary>WebSocket 监听的地址，默认为 0.0.0.0。</summary>
        public string WebSocketAddress
        {
            get { return _websocketaddress; }
            set { if (!WebSocketListening) _websocketaddress = value; }
        }

        /// <summary>WebSocket 监听的端口，默认为 8000。</summary>
        public int WebSocketPort
        {
            get { return _websocketport; }
            set { if (!WebSocketListening) _websocketport = NumberUtility.Restrict(value, 0, ushort.MaxValue); }
        }

        /// <summary></summary>
        public bool CommandListening
        {
            get { return false; }
        }

        /// <summary>命令服务监听的地址，默认为 0.0.0.0。</summary>
        public string CommandAddress
        {
            get { return _commandaddress; }
            set { if (!CommandListening) _commandaddress = value; }
        }

        /// <summary>命令服务监听的端口，默认为 8000。</summary>
        public int CommandPort
        {
            get { return _commandport; }
            set { if (!CommandListening) _commandport = NumberUtility.Restrict(value, 0, ushort.MaxValue); }
        }

        /// <summary></summary>
        public void Start() { PrivateStart(); }

        /// <summary></summary>
        public void Dispose()
        {
            if (_websocket != null)
            {
                lock (_websocket)
                {
                    _websocket.Close();
                    _websocket.Dispose();
                }
                _websocket = null;
            }

            DisposedAction?.Invoke();
        }

        #endregion

        #region fields

        private string _websocketaddress = "0.0.0.0";
        private int _websocketport = 8000;

        private string _commandaddress = "0.0.0.0";
        private int _commandport = 8000;

        private Dictionary<int, List<Connection>> _rooms = new Dictionary<int, List<Connection>>();

        private List<Connection> _lobby = new List<Connection>();

        private GenericServer _websocket = null;

        private UdpServer _command = null;

        #endregion

        #region private

        void RaiseError(params string[] text)
        {
            var merged = TextUtility.Merge(text);
            if (!TextUtility.IsBlank(merged)) ErrorAction?.Invoke(merged);
        }

        void RaiseExcepted(Exception exception)
        {
            if (exception != null) ExceptedAction?.Invoke(exception);
        }

        void RaiseConsole(params string[] text)
        {
            var merged = TextUtility.Merge(text);
            if (!TextUtility.IsBlank(merged)) Logger.Web.Text(this, merged);
        }

        private void PrivateStart()
        {
            if (_websocket != null) return;
            _websocket = new GenericServer();
            lock (_websocket)
            {
                try
                {
                    _websocket.Start(_websocketport, IPAddress.Parse(_websocketaddress));

                    _websocket.OnMessage += WebSocketReceived;
                    _websocket.OnOpen += WebsocketOnOpen;
                    _websocket.OnClose += WebsocketOnClose;
                    RaiseConsole("WebSocket 服务已启动。");
                }
                catch (Exception ex)
                {
                    _websocket = null;
                    RaiseError($"WebSocket 服务启动失败：{ex.Message()}");
                    return;
                }

                _command = new UdpServer();
                _command.Port = _commandport;
                _command.Excepted += (s, ex) => RaiseExcepted(ex);
                _command.Quitted += (s) => RaiseConsole("命令服务已退出。");
                _command.Started += (s) => RaiseConsole("命令服务已启动。");
                _command.Received += (s, e) => CommandReceived(s, e.IP, e.Port, e.Bytes);
                _command.Start();
            }
        }

        void WebsocketOnClose(Connection socket)
        {
            var room = GetRoomId(socket.QueryPath);
            var connections = LeaveRoom(socket, room);
            Send(connections, PackLeft(socket, room).ToString());
        }

        void WebsocketOnOpen(Connection socket)
        {
            var room = GetRoomId(socket.QueryPath);
            var connections = JoinRoom(socket, room);
            Send(connections, PackJoined(socket, room).ToString());
        }

        void WebSocketReceived(Connection socket, string content)
        {
            if (socket == null || string.IsNullOrEmpty(content))
            {
                RaiseConsole("WebSocket 服务接收到空消息。");
                return;
            }

            var input = Json.From(content);
            if (input == null)
            {
                RaiseConsole("WebSocket 服务接收到无效消息。");
                return;
            }

            var room = GetRoomId(socket.QueryPath);

            switch (input["type"])
            {
                case "command":
                    {
                        switch (input["command"])
                        {
                            case "exit":
                                Dispose();
                                break;
                        }
                    }
                    break;
                case "broadcast":
                    {
                        var json = PackMessage(socket, 0, input["text"], input.GetProperty("addition"));
                        if (_websocket != null)
                        {
                            lock (_websocket)
                            {
                                _websocket.Send(json.ToString());
                            }
                        }
                    }
                    break;
                case "message":
                    {
                        var connections = JoinRoom(socket, room);
                        var json = PackMessage(socket, room, input["text"], input.GetProperty("addition"));
                        Send(connections, json.ToString());
                    }
                    break;
            }
        }

        void Send(List<Connection> connections, string message)
        {
            if (connections == null) return;
            if (string.IsNullOrEmpty(message)) return;
            foreach (var connection in connections)
            {
                if (connection == null) continue;
                try
                {
                    connection.Send(message);
                }
                catch (Exception exception)
                {
                    if (ExceptedAction == null)
                    {
                        var a = connection.Address;
                        var p = connection.Port.ToString();
                        var e = exception.ToString();
                        Logger.Web.Error(this, TextUtility.Merge($"对 {a}:{p} 发送消息失败。"), exception.Message);
                    }
                    else
                    {
                        ExceptedAction(exception);
                    }
                }
            }
        }

        int GetRoomId(string path)
        {
            if (TextUtility.IsBlank(path)) return 0;
            var array = path.Split('/');
            if (array.Length > 1)
            {
                var caption = array[1];
                var room = NumberUtility.Int32(caption);
                var connections = new List<Connection>();
                if (room == 0 && !TextUtility.IsBlank(caption)) room = caption.GetHashCode();
                return room;
            }
            return 0;
        }

        List<Connection> JoinRoom(Connection socket, int room)
        {
            var connections = new List<Connection>();
            if (room == 0)
            {
                lock (_lobby)
                {
                    if (!_lobby.Contains(socket)) _lobby.Add(socket);
                    connections.AddRange(_lobby);
                }
            }
            else
            {
                lock (_rooms)
                {
                    if (_rooms.ContainsKey(room))
                    {
                        var value = _rooms[room];
                        lock (value)
                        {
                            if (!value.Contains(socket)) value.Add(socket);
                            connections.AddRange(value);
                        }
                    }
                    else
                    {
                        var value = new List<Connection>();
                        value.Add(socket);
                        connections.AddRange(value);
                        _rooms.Add(room, value);
                    }
                }
            }
            return connections;
        }

        List<Connection> LeaveRoom(Connection socket, int room)
        {
            var connections = new List<Connection>();
            if (room == 0)
            {
                lock (_lobby)
                {
                    if (_lobby.Contains(socket)) _lobby.Remove(socket);
                    connections.AddRange(_lobby);
                }
            }
            else
            {
                lock (_rooms)
                {
                    if (_rooms.ContainsKey(room))
                    {
                        var value = _rooms[room];
                        var empty = false;
                        lock (value)
                        {
                            if (value.Contains(socket)) value.Remove(socket);
                            empty = value.Count == 0;
                            if (!empty) connections.AddRange(value);
                            if (empty) _rooms.Remove(room);
                        }
                    }
                }
            }
            return connections;
        }

        Json PackJoined(Connection socket, int room = 0)
        {
            var json = Json.NewObject();
            json.SetProperty("utcstamp", ClockUtility.UtcStamp);
            json.SetProperty("nowstamp", ClockUtility.NowStamp);
            json.SetProperty("nowlucid", ClockUtility.LucidNow);
            json.SetProperty("address", socket.Address);
            json.SetProperty("port", socket.Port);
            json.SetProperty("room", room);
            json.SetProperty("type", "joined");
            return json;
        }

        Json PackLeft(Connection socket, int room = 0)
        {
            var json = Json.NewObject();
            json.SetProperty("utcstamp", ClockUtility.UtcStamp);
            json.SetProperty("nowstamp", ClockUtility.NowStamp);
            json.SetProperty("nowlucid", ClockUtility.LucidNow);
            json.SetProperty("address", socket.Address);
            json.SetProperty("port", socket.Port);
            json.SetProperty("room", room);
            json.SetProperty("type", "left");
            return json;
        }

        Json PackMessage(Connection socket, int room = 0, string text = null, Json addition = null)
        {
            var json = Json.NewObject();
            json.SetProperty("utcstamp", ClockUtility.UtcStamp);
            json.SetProperty("nowstamp", ClockUtility.NowStamp);
            json.SetProperty("nowlucid", ClockUtility.LucidNow);
            json.SetProperty("address", socket.Address);
            json.SetProperty("port", socket.Port);
            json.SetProperty("room", room);
            json.SetProperty("type", "message");
            json.SetProperty("text", text);
            json.SetProperty("addition", addition);
            return json;
        }

        void CommandReceived(object sender, string ip, int port, byte[] bytes)
        {

        }

        #endregion

        #region static

        /// <summary></summary>
        public static LogLevel LogLevel
        {
            get { return GenericServer.LogLevel; }
            set { GenericServer.LogLevel = value; }
        }

        #endregion

        #region program

        /// <summary>运行示例实例。</summary>
        public static void RunDemo(int chatTcpPort = 8000, int commandUdpPort = 8000)
        {
            var cs = new ChatServer();
            cs.WebSocketPort = chatTcpPort;
            cs.CommandPort = commandUdpPort;
            cs.Start();
            ChatServer.LogLevel = LogLevel.Debug;
            while (true)
            {
                var input = Console.ReadLine();
                if (input == "exit") break;
            }
            cs.Dispose();
        }

        static void RunLite()
        {
            var ws = new GenericServer();
            ws.OnOpen += (s) =>
            {
                ws.Send(s.Address, ":", s.Port.ToString(), "\n已连接\nHost: ", s.Host, "\nOrigin: ", s.Origin + "\nPath: ", s.QueryPath);
            };
            ws.OnClose += (s) =>
            {
                ws.Send(s.Address, ":", s.Port.ToString(), "\n已断开。");
            };
            ws.OnMessage += (s, m) =>
            {
                s.Send(s.Address, ":", s.Port.ToString(), "\n", m);
            };
            ws.Start();

            while (true)
            {
                var input = Console.ReadLine();
                if (input == "exit") break;
                ws.Send(ws.Address.ToString(), ":", ws.Port.ToString(), "\n", input);
            }

            ws.Close();
            ws.Dispose();
        }

        #endregion

    }

}

#endif
